#include "lib.h"
#include "bmp.h"
#include "filestream.h"
using namespace Library;


// These macros are similar to ALIGNINTxx, but work at a bit-level instead;
// which is important since we might be working with sub 8-bit images.
#define CEILING_BITWISE8(a)			(((a) +  7) &  ~7)
#define CEILING_BITWISE32(a)		(((a) + 31) & ~31)

//----------------------------------------------------------------------------
// Public
//
Bmp::Bmp()
{
	mFileHeader		= new FileHeader;
	mMetaInfoHeader	= new MetaInfoHeader;
	mImage			= 0;

	mInfoHeader		= &mMetaInfoHeader->infoHeader;
	mColorTable		= &mMetaInfoHeader->colorTable;

	reset();
}

Bmp::Bmp(Bmp & src)
{
	mFileHeader		= new FileHeader;
	mMetaInfoHeader	= new MetaInfoHeader;
	mImage			= 0;

	mInfoHeader		= &mMetaInfoHeader->infoHeader;
	mColorTable		= &mMetaInfoHeader->colorTable;

	reset();

	if (mFileHeader && src.mFileHeader)
		memcpy(mFileHeader, src.mFileHeader, sizeof FileHeader);
	
	if (mMetaInfoHeader && src.mMetaInfoHeader)
		memcpy(mMetaInfoHeader, src.mMetaInfoHeader, sizeof MetaInfoHeader);

	if (src.mImage)
	{
		mImage = new uint8[ src.imageSize() ];
		if (mImage)
			memcpy(mImage, src.mImage, src.imageSize());
	}
}

Bmp::~Bmp()
{
	delete mFileHeader;
	mFileHeader = 0;

	delete mMetaInfoHeader;
	mMetaInfoHeader = 0;

	delete [] mImage;
	mImage = 0;
}

bool8
Bmp::decodeImage()
{
	if ((compression() == rle4 && !decodeImageRLE4()) ||
		(compression() == rle8 && !decodeImageRLE8()))
		return false;

	if (compression() == rgb || compression() == bitfields)
		return decodeImageUnpad();

	return false;
}

bool8
Bmp::decodeImageUnpad()
{
	uint8 *		src;
	uint8 *		srcptr;
	uint32		srcrowbytes;
	uint8 *		dst;
	uint8 *		dstptr;
	uint32		dstrowbytes;
	uint32		y;

	if (!mImage)
		return false;

	srcrowbytes	= CEILING_BITWISE32(width() * bitsPerPixel()) / 8;
	dstrowbytes	= CEILING_BITWISE8 (width() * bitsPerPixel()) / 8;

	if (srcrowbytes == dstrowbytes)
		return true;

	src = srcptr = mImage;
	dst = dstptr = new uint8[ dstrowbytes * height() ];
	if (!dst)
		return false;

	for (y = 0; y < height(); y++)
	{
		memcpy(dstptr, srcptr, dstrowbytes);
		srcptr += srcrowbytes;
		dstptr += dstrowbytes;
	}

	image(dst);
	imageSize(dstrowbytes * height());

	return true;
}

bool8
Bmp::decodeImageRLE4()
{
	return false;
}

bool8
Bmp::decodeImageRLE8()
{
	return false;
}

bool8
Bmp::encodeImage()
{
	return false;
}

bool8
Bmp::encodeImagePad()
{
	uint8 *		src;
	uint8 *		srcptr;
	uint32		srcrowbytes;
	uint8 *		dst;
	uint8 *		dstptr;
	uint32		dstrowbytes;
	uint32		y;

	if (!mImage)
		return false;

	srcrowbytes	= CEILING_BITWISE8 (width() * bitsPerPixel()) / 8;
	dstrowbytes	= CEILING_BITWISE32(width() * bitsPerPixel()) / 8;
	if (srcrowbytes == dstrowbytes)
		return true;

	src = srcptr = mImage;
	dst = dstptr = new uint8[ dstrowbytes * height() ];
	if (!dst)
		return false;

	for (y = 0; y < height(); y++)
	{
		memcpy(dstptr, srcptr, srcrowbytes);
		memset(dstptr + srcrowbytes, 0, dstrowbytes - srcrowbytes);
		srcptr += srcrowbytes;
		dstptr += dstrowbytes;
	}

	image(dst);
	imageSize(dstrowbytes * height());

	return true;
}

bool8
Bmp::encodeImageRLE4()
{
	return false;
}

bool8
Bmp::encodeImageRLE8()
{
	return false;
}

bool8
Bmp::erectImage()
{
	if (imageOrientation() > 0)
	{
		if (flipImage())
		{
			height(-imageOrientation());
			return true;
		}

		return false;
	}

	return true;
}

bool8
Bmp::flipImage()
{
	uint8 *		src;
	uint8 *		srcptr;
	uint8 *		dst;
	uint8 *		dstptr;
	uint32		rowbytes;
	uint32		y;

	if (!mImage || (compression() != rgb && compression() != bitfields))
		return false;

	rowbytes	= CEILING_BITWISE8(width() * bitsPerPixel()) / 8;
	src			= mImage;
	srcptr		= src + (rowbytes * (height() - 1));
	dst			= new uint8[ imageSize() ];
	dstptr		= dst;

	if (!dst)
		return false;

	for (y = 0; y < height(); y++)
	{
		memcpy(dstptr, srcptr, rowbytes);
		srcptr -= rowbytes;
		dstptr += rowbytes;
	}

	mImage = dst;
	delete [] src;

	return true;
}

bool8
Bmp::invertImage()
{
	if (imageOrientation() < 0)
	{
		if (flipImage())
		{
			height(-imageOrientation());
			return true;
		}

		return false;
	}

	return true;
}

bool8
Bmp::load(const String & filename)
{
	FileStream	stream;
	
	if (!stream.open(filename, FileStream::reading))
		return false;

	reset();

	// Read the two required headers and authenticate.
	if (stream.read(mFileHeader, sizeof FileHeader) != sizeof FileHeader ||
		stream.read(mInfoHeader, sizeof InfoHeader) != sizeof InfoHeader ||
		mFileHeader->magic != BMP_MAGIC)
		return false;

	if (mInfoHeader->headerSize != sizeof InfoHeader)
	{
		if (mInfoHeader->headerSize == 56 && compression() == bitfields)
		{
			// Apparently Adobe (Photoshop) creates non-standard 16 bit (5-6-5)
			// bitmaps by setting BITMAPINFOHEADER->biSize to a value that
			// includes the size of BITMAPINFO->bmiColors when
			// BITMAPINFOHEADER->biCompression is BI_BITFIELDS.
			// 
			// Irritating as hell, but we have to accept their quirk.  In
			// short, this is not an error.
		}
		else
		{
			// Otherwise it's probably a BITMAPV4HEADER or BITMAPV5HEADER,
			// which we don't support.
			return false;
		}
	}

	// Read in the color table (if present).
	if (paletteEntries() &&
		stream.read(mColorTable, paletteSize()) != (int32)paletteSize())
		return false;

	// If an image offset is present use it as an authoritative source,
	// otherwise assume the image starts immediately after the color table.
	if (imageOffset() &&
		stream.seek(imageOffset(), Stream::beginning) != (int32)imageOffset())
		return false;

	// Apparently Adobe (Photoshop) pads their files to be 4-byte aligned.
	// This is a great idea except for the fact that their extra padding ends
	// up in their image size calculation and thus saved to disk.  To fix the
	// problem we'll ignore their value and let our code calculate it (below).
	if (compression() == rgb)
		imageSize(0);

	// An image size of zero (0) is valid when the image is not RLE compressed.
	// If it is zero (0), calculate what it should be (since we'll need it in
	// all the other methods anyway).
	if (!imageSize())
		imageSize((CEILING_BITWISE32(width() * bitsPerPixel()) / 8) * height());

	// Read in the image.  Upon failure, we leave mImage as is, so the calling
	// application can perform a postmortem.  Clean up will happen in the
	// destructor or upon the next load.
	mImage = new uint8[ imageSize() ];
	if (mImage && stream.read(mImage, imageSize()) != (int32)imageSize())
		return false;

	return decodeImage() && erectImage() ? true : false;
}

uint32
Bmp::paletteEntries()
{
	if (bitsPerPixel() <= 8)
		return colorsUsed() ? colorsUsed() : 1 << bitsPerPixel();

	if (compression() == bitfields)
		return 3;

	return 0;
}

bool8
Bmp::paletteIndexOf(uint8 red, uint8 green, uint8 blue, uint32 * index)
{
	uint32	i;

	for (i = 0; i < paletteEntries(); i++)
	{
		if (mColorTable->palette[i].red == red &&
			mColorTable->palette[i].green == green &&
			mColorTable->palette[i].blue == blue)
		{
			*index = i;
			return true;
		}
	}

	return false;
}

void
Bmp::reset()
{
	memset(mFileHeader, 0, sizeof FileHeader);
	memset(mMetaInfoHeader, 0, sizeof MetaInfoHeader);

	mFileHeader->magic		= BMP_MAGIC;
	mInfoHeader->headerSize	= sizeof InfoHeader;

	delete [] mImage;
	mImage = 0;
}

bool8
Bmp::save(const String & filename)
{
	Bmp		  	copy(*this);
	FileStream	stream;
	
	if (!stream.open(filename, FileStream::writing))
		return false;

	// REVISIT: Flipping and encoding needs to be selectable.
	if (!copy.invertImage() || !copy.encodeImagePad())
		return false;

	copy.imageOffset(sizeof FileHeader + sizeof InfoHeader + copy.paletteSize());
	copy.fileSize(copy.imageOffset() + copy.imageSize());

	if (stream.write(copy.fileHeader(), sizeof FileHeader) != sizeof FileHeader ||
		stream.write(copy.infoHeader(), sizeof InfoHeader) != sizeof InfoHeader ||
		stream.write(copy.colorTable(), copy.paletteSize()) != (int32)copy.paletteSize() ||
		stream.write(copy.image(), copy.imageSize()) != (int32)copy.imageSize())
		return false;

	return true;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn  void Bmp::loadBitmap(CBitmapBuffer *pBmpBuf)
///
/// This method takes a CBitmapBuffer and uses it to populate all of the BMP specific structures 
/// so that a new .BMP can be created.
///
/// kharmon.  01-09-07.  Adding ability to decompile a .rsc.
///////////////////////////////////////////////////////////////////////////////////////////////////
void Bmp::loadBitmap(CBitmapBuffer *pBmpBuf)
{
    reset();    
   
    mInfoHeader->compression = rgb;
    mInfoHeader->planes = 1;
    mInfoHeader->width  = pBmpBuf->cSize.x;
    mInfoHeader->height = -pBmpBuf->cSize.y;
    
    switch(pBmpBuf->cImageVersion)
    {
        case BMP_8_BIT:
        {
            mInfoHeader->colorsImportant = 256;
            mInfoHeader->colorsUsed      = 256;
            mInfoHeader->bitsPerPixel    = 8;
            break;
        }
        case BMP_16_BIT:
        {
            mInfoHeader->compression     = bitfields;
            mInfoHeader->bitsPerPixel    = 16;
            break;
        }
        case BMP_24_BIT:
        {
            mInfoHeader->bitsPerPixel    = 24;
            break;
        }
        case BMP_32_BIT:
        {
            mInfoHeader->bitsPerPixel    = 32;
            break;
        }
        default:
        {
            mInfoHeader->bitsPerPixel    = 16;
            break;
        }
    }

    mInfoHeader->imageSize = (CEILING_BITWISE8(mInfoHeader->width * mInfoHeader->bitsPerPixel) / 8) * pBmpBuf->cSize.y;

    mImage = new uint8[mInfoHeader->imageSize];
    memcpy(mImage, (uint8 *)((uint32)pBmpBuf + sizeof(CBitmapBuffer) + pBmpBuf->cImageOffset), mInfoHeader->imageSize);

    mFileHeader->fileSize = sizeof(FileHeader) + sizeof(InfoHeader) + mInfoHeader->imageSize;

    if(pBmpBuf->cImageVersion==BMP_16_BIT)
    {
        // if this is a 16 bit file, we want to save it as RGB565, so set up the bitfield mask accordingly.
        mFileHeader->imageOffset += 12;
        mFileHeader->fileSize    += 12;

        memset(mMetaInfoHeader->colorTable.palette, 0, sizeof(mMetaInfoHeader->colorTable.palette));

        uint32 *punMasks = (uint32 *) mMetaInfoHeader->colorTable.palette;
        punMasks[0]=0x0000f800;
        punMasks[1]=0x000007E0;
        punMasks[2]=0x0000001F;
    }
    else if(pBmpBuf->cPaletteOffset!=0)
    {
        // if we have a palette, write it out, adjusting all of the offsets and lengths.
        mFileHeader->imageOffset += 1024;
        mFileHeader->fileSize    += 1024;        

        memset(mMetaInfoHeader->colorTable.palette, 0, sizeof(mMetaInfoHeader->colorTable.palette));

        uint8 *pucPal = (uint8 *)((uint32)pBmpBuf + sizeof(CBitmapBuffer) + pBmpBuf->cPaletteOffset);

        for(int nI=0;nI<256;++nI)
        {
            // we store RGBTRIPLEs but the BMP file format wants RGBQUADs.
            mMetaInfoHeader->colorTable.palette[nI].blue  = pucPal[nI*3];
            mMetaInfoHeader->colorTable.palette[nI].green = pucPal[nI*3 + 1];
            mMetaInfoHeader->colorTable.palette[nI].red   = pucPal[nI*3 + 2];
        }
    }
}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn   bool8 Bmp::loadAlphaBitmap(CBitmapBuffer *pBmpBuf)
///
/// This method takes a CBitmapBuffer and extracts the "alpha" information.  Then, the BMP specific
/// structures are initialized so that we can write this "alpha" information out to a .BMP file
/// that matches the input type that we expect for "XXXX_a.bmp" alpha definition files.
///
/// kharmon.  01-09-07.  Adding ability to decompile a .rsc.
///////////////////////////////////////////////////////////////////////////////////////////////////
bool8 Bmp::loadAlphaBitmap(CBitmapBuffer *pBmpBuf)
{
    reset();    

    if(pBmpBuf->cAlphaOffset==0)
    {
        return false;
    }
   
    mInfoHeader->compression = 0;
    mInfoHeader->planes = 1;
    mInfoHeader->width  = pBmpBuf->cSize.x;
    mInfoHeader->height = -pBmpBuf->cSize.y;
    
    mInfoHeader->colorsImportant = 256;
    mInfoHeader->colorsUsed      = 256;
    mInfoHeader->bitsPerPixel    = 8;           // these "alpha" files are always 256 color bmps.

    mInfoHeader->imageSize = (CEILING_BITWISE8(mInfoHeader->width * mInfoHeader->bitsPerPixel) / 8) * pBmpBuf->cSize.y;

    mImage = new uint8[mInfoHeader->imageSize];
    memcpy(mImage, (uint8 *)((uint32)pBmpBuf + sizeof(CBitmapBuffer) + pBmpBuf->cAlphaOffset), mInfoHeader->imageSize);

    mFileHeader->fileSize = sizeof(FileHeader) + sizeof(InfoHeader) + mInfoHeader->imageSize;

    mFileHeader->imageOffset += 1024;
    mFileHeader->fileSize    += 1024;        

    memset(mMetaInfoHeader->colorTable.palette, 0, sizeof(mMetaInfoHeader->colorTable.palette));

    for(int nI=0;nI<256;++nI)
    {
        // we just add a gray-scale palette, because that's what we're used to seeing when looking
        // at these "alpha" bitmaps.  The palette values don't actually get used.
        mMetaInfoHeader->colorTable.palette[nI].blue  = nI;
        mMetaInfoHeader->colorTable.palette[nI].green = nI;
        mMetaInfoHeader->colorTable.palette[nI].red   = nI;
    }

    return true;
}


